package Test;

import java.io.*;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArrayList;

public class Server1 {

    public static int udpPort = 6999;
    public static int tcpPort = 6665;

    public static void main(String[] args) throws IOException {
        //使用一个空线程    来打开静态代码块中的udp服务
        new Thread(new MyRunnable(new Socket())).start();
        ServerSocket serverSocket = new ServerSocket(tcpPort);
        System.out.println("服务器启动了...");
        while (true) {
            //即每个客户端都新建一个进程
            Socket accept = serverSocket.accept();
            new Thread(new MyRunnable(accept)).start();
        }
    }

    static class MyRunnable implements Runnable {
        //使用线程安全的ConcurrentHashMap保存用户信息    记录当前在线的人
        volatile public static Map<String, Player> map = new ConcurrentHashMap<>();
        //由于某种原因   就不移除已经过时的socket
        volatile public static List<Socket> list = new CopyOnWriteArrayList<>();

        //日志map   用来记录曾经登录过的用户
        public static Map<String, Player> logMap = new ConcurrentHashMap<>();

        Socket socket;
        Player client;

        public MyRunnable(Socket accept) {
            this.socket = accept;
            list.add(accept);
        }

        //静态代码块   读取曾经登录的map
        static {
            try {
                File file = new File("src/log.txt");
                BufferedReader br = new BufferedReader(new FileReader(file));
                String s = br.readLine();//第一行作为注释
                s = br.readLine();
                while (s != null && !s.equals("")) {
                    String[] s1 = s.split(" ");
                    try {
                        Player player = new Player(s1[0], Integer.parseInt(s1[1]), Integer.parseInt(s1[2]),
                                Integer.parseInt(s1[3]), Integer.parseInt(s1[4]));
                        logMap.put(s1[0], player);
                    } catch (Exception e) {
                        //如果解析错误，解析下一个
                    }
                    s = br.readLine();
                }
                br.close();
            } catch (Exception e) {
                System.out.println("读取日志文件失败");
            }

            //打开udp服务
            new Thread(() -> {
                DatagramSocket ds = null;
                try {
                    ds = new DatagramSocket(udpPort);

                    while (true) {
                        try {
                            byte[] data = new byte[1024];
                            DatagramPacket dp = new DatagramPacket(data, data.length);
                            ds.receive(dp);

                            //根据data的内容   返回相应的内容bytes
                            byte[] bytes = null;
                            if (data[0] == 4)//PLAYER_STATE_REQUEST客户端发送给服务器获得指定玩家的状态
                            {
                                //解析收到的包
                                String requestName = new String(Arrays.copyOfRange(data, 5, 15)).trim();

                                Player player = null;//如果有就使用有的
                                if (logMap.containsKey(requestName))
                                    player = logMap.get(requestName);
                                else {
                                    player = new Player(requestName);//没有就新建
                                    logMap.put(requestName, player);
                                }

                                bytes = new byte[28];
                                bytes[0] = 5;//msgType

                                bytes[1] = data[1];//唯一id
                                bytes[2] = data[2];
                                bytes[3] = data[3];
                                bytes[4] = data[4];

                                int count = 5;
                                byte[] bytes1 = Arrays.copyOf(player.name.getBytes(), 10);
                                for (byte b : bytes1) {
                                    bytes[count++] = b;
                                }
                                byte[] bytes3 = toBytes(player.hp);
                                for (byte b : bytes3) {
                                    bytes[count++] = b;
                                }
                                byte[] bytes4 = toBytes(player.exp);
                                for (byte b : bytes4) {
                                    bytes[count++] = b;
                                }
                                bytes[count++] = (byte) player.x;
                                bytes[count] = (byte) player.y;
                            } else if (data[0] == 6)//SAVE_STATE_REQUEST  客户端向服务器发送消息，以存储玩家的状态
                            {
                                System.out.println("收到SAVE_STATE_REQUEST中");
                                bytes = new byte[8];
                                bytes[0] = 7;//msgType

                                bytes[1] = data[1];//唯一id
                                bytes[2] = data[2];
                                bytes[3] = data[3];
                                bytes[4] = data[4];

                                bytes[5] = 0;//假定成功
                                try {
                                    //SAVE_STATE
                                    String saveName = new String(Arrays.copyOfRange(data, 5, 15)).trim();
                                    int saveHP = toInt(Arrays.copyOfRange(data, 15, 19));
                                    int saveEXP = toInt(Arrays.copyOfRange(data, 19, 23));
                                    System.out.println("保存的用户为："+new Player(saveName, saveHP, saveEXP, data[23], data[24]));
                                    logMap.put(saveName, new Player(saveName, saveHP, saveEXP, data[23], data[24]));
                                    //保存logMap的信息到文件
                                    try {
                                        File file = new File("src\\log.txt");
                                        BufferedWriter bw = new BufferedWriter(new FileWriter(file));
                                        bw.write("注释：单个空格隔开，每一行依次输入String name;int hp;int exp;int x;int y;\n");
                                        for (String s : logMap.keySet()) {
                                            Player player = logMap.get(s);
                                            bw.write(player.name + " " + player.hp + " " + player.exp
                                                    + " " + player.x + " " + player.y + "\n");
                                        }
                                        bw.close();
                                    } catch (Exception e) {
                                        //如果写入文件出错
                                        System.out.println("保存日志文件出错");
                                    }
                                } catch (Exception e) {
                                    bytes[5] = 1;//保存失败
                                }

                            } else {//如果是不认识的请求
                                continue;
                            }

                            DatagramPacket Sendpacket = new DatagramPacket(bytes, bytes.length, dp.getAddress(), dp.getPort());
                            ds.send(Sendpacket);
                        } catch (Exception e) {
                            System.out.println("udp出错了一次");
                        }
                    }
                } catch (Exception e) {
                    System.out.println("server的udp服务出现异常");
                }
            }).start();


        }

        //整个run处理单个客户端   把所有代码放在try catch里   即客户端和服务器有任何错误  就保存信息然后退出
        @Override
        public void run() {

            //处理单个客户端的请求
            while (true)//一旦logout就会break   该线程就会结束
            {
                try {//检查连接
                    socket.sendUrgentData(0xFF);
                } catch (Exception e) {
                    if (client != null) {
                        System.out.println("与 " + client.name + " 断开连接");
                        map.remove(client.name);
                        return;
                    } else {
                        System.out.println("某人未登录，就断开了连接");
                        break;
                    }
                }

                try {
                    InputStream is = socket.getInputStream();
                    byte[] bytes = new byte[4];
                    is.read(bytes);
                    int length = Byte.toUnsignedInt(bytes[1]) * (int) Math.pow(2, 8)
                            + Byte.toUnsignedInt(bytes[2]);

                    if (bytes[3] == 1)//loginRequest过来  需要返回loginreply和movenotify
                    {
                        if (client != null)//如果已经登录  返回无效编码1
                        {
                            invalid(1);
                            continue;
                        }
                        byte[] bytes1 = new byte[10];//读取name
                        is.read(bytes1);

                        byte[] bytes2 = new byte[4];
                        is.read(bytes2);
                        int reqHP = toInt(bytes2);
                        byte[] bytes3 = new byte[4];
                        is.read(bytes3);
                        int reqEXP = toInt(bytes3);
                        int reqX = is.read();
                        int reqY = is.read();

                        //操作
                        String name = new String(bytes1).trim();

                        if (map.containsKey(name))//name当前已被他人使用
                        {
                            loginReply(1);
                        } else {
                            //需要返回loginreply和movenotify
                            Player player = new Player(name, reqHP, reqEXP, reqX, reqY);
                            client = player;
                            map.put(name, player);

                            loginReply(0);//登录成功

                            //此时我不知道先登陆者的位置，所以此处需要广播所有人的位置
                            for (String s : map.keySet()) {
                                moveNotify(map.get(s));
                            }
                        }

                    }
                    if (client == null)//如果没有登录  不能执行指令
                    {
                        System.out.println("该请求头导致无效消息" + Arrays.toString(bytes));
                        invalid(0);//如果没有登录  返回无效编码0
                        continue;
                    }

                    if (bytes[3] == 3)//move   需要返回movenotify
                    {
                        byte[] bytes1 = new byte[4];
                        is.read(bytes1);
                        if (bytes1[0] == 0)//north
                        {
                            client.y -= 3;
                        } else if (bytes1[0] == 1)//south
                        {
                            client.y += 3;
                        } else if (bytes1[0] == 2)//east
                        {
                            client.x += 3;
                        } else if (bytes1[0] == 3)//west
                        {
                            client.x -= 3;
                        }
                        if (client.x < 0)
                            client.x += 100;
                        if (client.y < 0)
                            client.y += 100;
                        client.x %= 100;
                        client.y %= 100;
                        //返回movenotify
                        moveNotify();
                    }
                    if (bytes[3] == 5)//attack
                    {
                        byte[] bytes1 = new byte[10];//读取victim name
                        is.read(bytes1);

                        byte[] bytes2 = new byte[2];//读取padding
                        is.read(bytes2);

                        String victim = new String(bytes1).trim();

                        //返回attackNotify
                        attackNotify(victim);

                    }
                    if (bytes[3] == 7)//speak
                    {
                        //length可以使用
                        byte[] bytes1 = new byte[length - 4];
                        is.read(bytes1);
                        int end = 0;
                        for (int i = 0; i < bytes1.length; i++) {
                            if (bytes1[i] == 0) {
                                end = i;
                                break;
                            }
                        }
                        String msg = new String(Arrays.copyOf(bytes1, end + 1)).trim();

                        System.out.println("speak:" + msg);
                        //调用speakNotify
                        speakNotify(msg);
                    }
                    if (bytes[3] == 9)//logout
                    {
                        //调用logoutNotify
                        logoutNotify();
                        map.remove(client.name);

                        break;//logout之后直接结束该线程
                    }

                } catch (IOException e) {
                    e.printStackTrace();
                    System.out.println("在一次处理消息的时候出错啦！！！");
                    continue;
                }
            }

            //如果logout  就会运行该代码
            if (client != null) {
                map.remove(client.name);//移除logout的人
                client = null;
            }

        }

        public static int toInt(byte[] bytes) {//把长度为4的bytes数组转化为int
            return Byte.toUnsignedInt(bytes[0]) * (int) Math.pow(2, 24) +
                    Byte.toUnsignedInt(bytes[1]) * (int) Math.pow(2, 16) +
                    Byte.toUnsignedInt(bytes[2]) * (int) Math.pow(2, 8) +
                    Byte.toUnsignedInt(bytes[3]);
        }

        public void invalid(int code) throws IOException {
            OutputStream os = socket.getOutputStream();
            byte[] bytes = new byte[8];
            bytes[0] = 4;
            bytes[1] = 0;
            bytes[2] = 8;
            bytes[3] = 11;

            bytes[4] = (byte) code;
            System.out.println("invalid消息" + code + " " + Arrays.toString(bytes));
            os.write(bytes);
        }

        public void logoutNotify() throws IOException {
            byte[] bytes = new byte[24];
            bytes[0] = 4;
            bytes[1] = 0;
            bytes[2] = 24;
            bytes[3] = 10;

            byte[] bytes1 = Arrays.copyOf(client.name.getBytes(), 10);

            int count = 4;
            for (byte b : bytes1) {//写入名字
                bytes[count++] = b;
            }

            byte[] bytes2 = toBytes(client.hp);
            bytes[14] = bytes2[0];
            bytes[15] = bytes2[1];
            bytes[16] = bytes2[2];
            bytes[17] = bytes2[3];

            byte[] bytes3 = toBytes(client.exp);
            bytes[18] = bytes3[0];
            bytes[19] = bytes3[1];
            bytes[20] = bytes3[2];
            bytes[21] = bytes3[3];

            bytes[22] = (byte) client.x;
            bytes[23] = (byte) client.y;


            System.out.println(client.name + "的logoutNotify " + Arrays.toString(bytes));

            for (int i = 0; i < list.size(); i++) {
                try {
                    OutputStream os = list.get(i).getOutputStream();
                    os.write(bytes);
                } catch (Exception e) {
                    continue;
                }
            }

        }

        public void speakNotify(String msg) throws IOException {

            int length = 4 + 10 + msg.getBytes().length;
            while (length % 4 != 0)
                length++;
            byte[] bytes2 = toBytes(length);
            byte[] bytes = new byte[length];
            bytes[0] = 4;
            bytes[1] = bytes2[2];
            bytes[2] = bytes2[3];
            bytes[3] = 8;

            byte[] bytes1 = Arrays.copyOf(client.name.getBytes(), 10);

            int count = 4;
            for (byte b : bytes1) {//写入名字
                bytes[count++] = b;
            }

            byte[] bytes3 = msg.getBytes();


            for (byte b : bytes3) {//写入msg
                bytes[count++] = b;
            }
//            System.out.println("speak:"+Arrays.toString(bytes));
            System.out.println(client.name + "的speakNotify " + Arrays.toString(bytes));

            for (int i = 0; i < list.size(); i++) {
                try {
                    OutputStream os = list.get(i).getOutputStream();
                    os.write(bytes);
                } catch (Exception e) {
                    continue;
                }

            }

        }

        public void attackNotify(String victim) throws IOException {
            byte[] bytes = new byte[32];
            bytes[0] = 4;
            bytes[1] = 0;
            bytes[2] = 32;
            bytes[3] = 6;

            byte[] bytes1 = client.name.getBytes();
            byte[] bytes2 = victim.getBytes();
            byte[] bytes3 = Arrays.copyOf(bytes1, 10);
            byte[] bytes4 = Arrays.copyOf(bytes2, 10);
            int count = 4;
            for (byte b : bytes3) {
                bytes[count++] = b;
            }
            for (byte b : bytes4) {
                bytes[count++] = b;
            }
            byte damage = (byte) (10 + new Random().nextInt(11));//damage
            bytes[count++] = damage;

            client.exp += damage;
            map.get(victim).hp -= damage;
            if (map.get(victim).hp <= 0)//如果玩家死亡
                map.get(victim).hp = 0;
            byte[] bytes5 = toBytes(map.get(victim).hp);
            for (byte b : bytes5) {
                bytes[count++] = b;
            }

            System.out.println(client.name + "的attackNotify " + Arrays.toString(bytes));
//            System.out.println("受害者当前hp为" + map.get(victim).hp);
            for (int i = 0; i < list.size(); i++) {
                try {
                    OutputStream os = list.get(i).getOutputStream();
                    os.write(bytes);
                } catch (Exception e) {
                    continue;
                }

            }
            if (map.get(victim).hp <= 0)//如果玩家死亡
            {
                map.get(victim).hp = 30 + new Random().nextInt(21);
                map.get(victim).x = new Random().nextInt(100);
                map.get(victim).y = new Random().nextInt(100);
                moveNotify(map.get(victim));
            }
        }


        public void moveNotify() throws IOException {
            byte[] bytes = new byte[24];
            bytes[0] = 4;
            bytes[1] = 0;
            bytes[2] = 24;
            bytes[3] = 4;

            byte[] bytes1 = client.name.getBytes();
            byte[] bytes2 = Arrays.copyOf(bytes1, 10);
            int count = 4;
            for (byte b : bytes2) {
                bytes[count++] = b;
            }
            bytes[count++] = (byte) client.x;
            bytes[count++] = (byte) client.y;

            byte[] bytes3 = toBytes(client.hp);
            for (byte b : bytes3) {
                bytes[count++] = b;
            }
            byte[] bytes4 = toBytes(client.exp);
            for (byte b : bytes4) {
                bytes[count++] = b;
            }
            System.out.println(client.name+"的moveNotify "+Arrays.toString(bytes));
            for (int i = 0; i < list.size(); i++) {
                try {
                    OutputStream os = list.get(i).getOutputStream();
                    os.write(bytes);
                } catch (Exception e) {
                    continue;
                }

            }
        }

        //有玩家死亡的时候发送   或者有玩家登陆的时候发送
        public void moveNotify(Player player) throws IOException {
            byte[] bytes = new byte[24];
            bytes[0] = 4;
            bytes[1] = 0;
            bytes[2] = 24;
            bytes[3] = 4;

            byte[] bytes1 = player.name.getBytes();
            byte[] bytes2 = Arrays.copyOf(bytes1, 10);
            int count = 4;
            for (byte b : bytes2) {
                bytes[count++] = b;
            }
            bytes[count++] = (byte) player.x;
            bytes[count++] = (byte) player.y;

            byte[] bytes3 = toBytes(player.hp);
            for (byte b : bytes3) {
                bytes[count++] = b;
            }
            byte[] bytes4 = toBytes(player.exp);
            for (byte b : bytes4) {
                bytes[count++] = b;
            }
            System.out.println("特殊的movenotify" + Arrays.toString(bytes));
            for (int i = 0; i < list.size(); i++) {
                try {
                    OutputStream os = list.get(i).getOutputStream();
                    os.write(bytes);
                } catch (Exception e) {
                    continue;
                }

            }
        }


        /*
         * @code [0,1]   0表示登录成功   1表示name以被占用
         * */
        public void loginReply(int code) throws IOException {

            OutputStream os = socket.getOutputStream();
            byte[] bytes = new byte[16];
            bytes[0] = 4;
            bytes[1] = 0;
            bytes[2] = 16;
            bytes[3] = 2;

            int count = 4;
            bytes[count++] = (byte) code;
            if (code == 1) {
                os.write(bytes);
                return;
            }
            byte[] bytes1 = toBytes(client.hp);
            for (byte b : bytes1) {
                bytes[count++] = b;
            }
            byte[] bytes2 = toBytes(client.exp);
            for (byte b : bytes2) {
                bytes[count++] = b;
            }
            bytes[count++] = (byte) client.x;
            bytes[count++] = (byte) client.y;


            System.out.println(client.name + "的loginReply " + socket + Arrays.toString(bytes));
            os.write(bytes);

        }


        public static byte[] toBytes(int a) {
            byte[] temp = new byte[4];
            temp[0] = (byte) (a >> 24);
            temp[1] = (byte) (a >> 16);
            temp[2] = (byte) (a >> 8);
            temp[3] = (byte) a;
            return temp;
        }



        static class Player {
            public String name;
            public int hp;
            public int exp;
            public int x;
            public int y;

            public Player() {
            }

            public Player(String name) {
                this.name = name;
                hp = 100 + new Random().nextInt(21);
                exp = 0;
                x = new Random().nextInt(100);
                y = new Random().nextInt(100);
            }

            public Player(String name, int hp, int exp, int x, int y) {
                this.name = name;
                this.hp = hp;
                this.exp = exp;
                this.x = x;
                this.y = y;
            }

            @Override
            public String toString() {
                return "Player{" +
                        "name='" + name + '\'' +
                        ", hp=" + hp +
                        ", exp=" + exp +
                        ", x=" + x +
                        ", y=" + y +
                        '}';
            }
        }
    }
}


